/**
 * 
 */
package com.rpg.base.flyweight;

import java.util.ArrayList;

import com.dalonedrau.jogl.engine.GameApplet;
import com.dalonedrau.jogl.engine.Interactive;
import com.dalonedrau.pool.PooledStringBuilder;
import com.dalonedrau.pool.StringBuilderPool;
import com.dalonedrau.watchable.Watchable;
import com.dalonedrau.watchable.Watcher;
import com.rpg.base.interactive.flyweight.BaseInteractiveObject;
import com.rpg.base.interactive.flyweight.InteractiveObject;

/**
 * @author Donald
 */
public abstract class RPGCharacter extends InteractiveObject
		implements Watchable {
	/** the base ability scores. */
	private float[]				baseAbilityScores;
	/**
	 * the reference ids of all items equipped by the {@link RPGCharacter},
	 * indexed by equipment slot.
	 */
	private int[]				equippedItems;
	/** any flags being applied to the {@link RPGCharacter}. */
	private long				flags		= 0;
	/** the full ability scores. */
	private float[]				fullAbilityScores;
	/** the {@link RPGCharacter}'s gender. */
	private int					gender		= -1;
	/** the {@link RPGCharacter}'s level. */
	private int					level		= 0;
	/** the ability score modifiers. */
	private float[]				modAbilityScores;
	/** the {@link RPGCharacter}'s name. */
	private char[]				name;
	/** the {@link RPGCharacter}'s Profession. */
	private int					profession	= -1;
	/** the {@link RPGCharacter}'s Race. */
	private int					race		= -1;
	/**
	 * the list of {@link Watcher}s associated with this {@link RPGCharacter}.
	 */
	private ArrayList<Watcher>	watchers	= new ArrayList<Watcher>();
	/** the {@link RPGCharacter}'s experience points. */
	private long				xp;
	/** Creates a new instance of {@link RPGCharacter}. */
	protected RPGCharacter() {
		super(Interactive.getInstance().getNextId());
		try {
			Interactive.getInstance().addIO(this);
		} catch (Exception ex) {
			ErrorHandler.getInstance().fatalError(ex);
		}
		init();
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void addWatcher(final Watcher watcher) {
		watchers.add(watcher);
	}
	/**
	 * Adjusts the score modifier for a specific character ability.
	 * @param ability the ability
	 * @param score the new score
	 * @throws Exception if the ability was never defined
	 */
	public final void adjModAbilityScore(final int ability, final float score)
			throws Exception {
		if (ability < 0
				|| ability >= baseAbilityScores.length) {
			PooledStringBuilder sb =
					StringBuilderPool.getInstance().getStringBuilder();
			sb.append("Error - ability ");
			sb.append(ability);
			sb.append(" is outside array bounds.");
			Exception ex = new Exception(sb.toString());
			sb.returnToPool();
			throw ex;
		}
		modAbilityScores[ability] += score;
	}
	/**
	 * Adjusts the {@link RPGCharacter}'s experience points.
	 * @param val the amount adjusted by
	 */
	public final void adjustXp(final long val) {
		xp += val;
	}
	/** Clears all flags that were set. */
	public final void clearFlags() {
		flags = 0;
	}
	/** Clears all ability score modifiers. */
	public final void clearModAbilityScores() {
		for (int i = 0; i < modAbilityScores.length; i++) {
			modAbilityScores[i] = 0;
		}
	}
	/**
	 * Gets the base score for a specific character ability.
	 * @param ability the ability
	 * @return int
	 * @throws Exception if the ability was never defined
	 */
	public final float getBaseAbilityScore(final int ability) throws Exception {
		if (ability < 0
				|| ability >= baseAbilityScores.length) {
			PooledStringBuilder sb =
					StringBuilderPool.getInstance().getStringBuilder();
			sb.append("Error - ability ");
			sb.append(ability);
			sb.append(" is outside array bounds.");
			Exception ex = new Exception(sb.toString());
			sb.returnToPool();
			throw ex;
		}
		return baseAbilityScores[ability];
	}
	/**
	 * Gets the reference id of the item the {@link RPGCharacter} has equipped
	 * at a specific equipment slot.
	 * @param slot the equipment slot
	 * @return int
	 * @throws Exception if the equipment slot was never defined
	 */
	public final int getEquippedItem(final int slot) throws Exception {
		int id = -1;
		if (slot < 0
				|| slot >= equippedItems.length) {
			PooledStringBuilder sb =
					StringBuilderPool.getInstance().getStringBuilder();
			sb.append("Error - equipment slot ");
			sb.append(slot);
			sb.append(" is outside array bounds.");
			Exception ex = new Exception(sb.toString());
			sb.returnToPool();
			throw ex;
		}
		id = equippedItems[slot];
		return id;
	}
	/**
	 * Gets the full score for a specific character ability.
	 * @param ability the ability
	 * @return int
	 * @throws Exception if the ability was never defined
	 */
	public final float getFullAbilityScore(final int ability) throws Exception {
		if (ability < 0
				|| ability >= baseAbilityScores.length) {
			PooledStringBuilder sb =
					StringBuilderPool.getInstance().getStringBuilder();
			sb.append("Error - ability ");
			sb.append(ability);
			sb.append(" is outside array bounds.");
			Exception ex = new Exception(sb.toString());
			sb.returnToPool();
			throw ex;
		}
		return fullAbilityScores[ability];
	}
	/**
	 * Gets the {@link RPGCharacter}'s gender.
	 * @return int
	 */
	public final int getGender() {
		return gender;
	}
	/**
	 * Gets the {@link RPGCharacter}'s level.
	 * @return int
	 */
	public final int getLevel() {
		return level;
	}
	/**
	 * Gets the score modifier for a specific character ability.
	 * @param ability the ability
	 * @return int
	 * @throws Exception if the ability was never defined
	 */
	public final float getModAbilityScore(final int ability) throws Exception {
		if (ability < 0
				|| ability >= baseAbilityScores.length) {
			PooledStringBuilder sb =
					StringBuilderPool.getInstance().getStringBuilder();
			sb.append("Error - ability ");
			sb.append(ability);
			sb.append(" is outside array bounds.");
			Exception ex = new Exception(sb.toString());
			sb.returnToPool();
			throw ex;
		}
		return modAbilityScores[ability];
	}
	/**
	 * Gets the {@link RPGCharacter}'s name.
	 * @return char[]
	 */
	public final char[] getName() {
		return name;
	}
	/**
	 * Gets the {@link RPGCharacter}'s Profession.
	 * @return int
	 */
	public final int getProfession() {
		return profession;
	}
	/**
	 * Gets the {@link RPGCharacter}'s Race.
	 * @return int
	 */
	public final int getRace() {
		return race;
	}
	/**
	 * Gets the {@link RPGCharacter}'s experience points.
	 * @return int
	 */
	public final long getXp() {
		return xp;
	}
	/**
	 * Determines if the {@link RPGCharacter} has a specific flag.
	 * @param flag the flag
	 * @return true if the {@link RPGCharacter} has the flag; false otherwise
	 */
	public final boolean hasFlag(final long flag) {
		return (flags & flag) == flag;
	}
	/** Initializes the system specific characteristics. */
	protected abstract void init();
	/**
	 * Initializes the abilities.
	 * @param total the total number of abilities
	 */
	protected final void initAbilityScores(final int total) {
		baseAbilityScores = new float[total];
		modAbilityScores = new float[total];
		fullAbilityScores = new float[total];
	}
	/**
	 * Initializes the items the {@link RPGCharacter} has equipped.
	 * @param total the total number of equipment slots
	 */
	protected final void initEquippedItems(final int total) {
		equippedItems = new int[total];
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void notifyWatchers() {
		for (int i = 0; i < watchers.size(); i++) {
			watchers.get(i).watchUpdated(this);
		}
	}
	/**
	 * Removes a flag.
	 * @param flag the flag
	 */
	public final void removeFlag(final long flag) {
		flags &= ~flag;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void removeWatcher(final Watcher watcher) {
		watchers.remove(watcher);
	}
	/** Sets all full ability scores. */
	public final void setAllFullAbilityScores() {
		for (int i = 0; i < fullAbilityScores.length; i++) {
			fullAbilityScores[i] = baseAbilityScores[i] + modAbilityScores[i];
		}
	}
	/**
	 * Sets the base score for a specific character ability.
	 * @param ability the ability
	 * @param score the new score
	 * @throws Exception if the ability was never defined
	 */
	public final void setBaseAbilityScore(final int ability, final float score)
			throws Exception {
		if (ability < 0
				|| ability >= baseAbilityScores.length) {
			PooledStringBuilder sb =
					StringBuilderPool.getInstance().getStringBuilder();
			sb.append("Error - ability ");
			sb.append(ability);
			sb.append(" is outside array bounds.");
			Exception ex = new Exception(sb.toString());
			sb.returnToPool();
			throw ex;
		}
		baseAbilityScores[ability] = score;
	}
	/**
	 * Sets the reference id of the item the {@link RPGCharacter} has equipped
	 * at a specific equipment slot.
	 * @param slot the equipment slot
	 * @param item the item being equipped
	 * @throws Exception if the equipment slot was never defined
	 */
	public final void setEquippedItem(final int slot,
			final BaseInteractiveObject item) throws Exception {
		if (slot < 0
				|| slot >= equippedItems.length) {
			PooledStringBuilder sb =
					StringBuilderPool.getInstance().getStringBuilder();
			sb.append("Error - equipment slot ");
			sb.append(slot);
			sb.append(" is outside array bounds.");
			Exception ex = new Exception(sb.toString());
			sb.returnToPool();
			throw ex;
		}
		equippedItems[slot] = item.getRefId();
	}
	/**
	 * Sets the reference id of the item the {@link RPGCharacter} has equipped
	 * at a specific equipment slot.
	 * @param slot the equipment slot
	 * @param id the item's reference id
	 * @throws Exception if the equipment slot was never defined
	 */
	public final void setEquippedItem(final int slot, final int id)
			throws Exception {
		if (slot < 0
				|| slot >= equippedItems.length) {
			PooledStringBuilder sb =
					StringBuilderPool.getInstance().getStringBuilder();
			sb.append("Error - equipment slot ");
			sb.append(slot);
			sb.append(" is outside array bounds.");
			Exception ex = new Exception(sb.toString());
			sb.returnToPool();
			throw ex;
		}
		equippedItems[slot] = id;
	}
	/**
	 * Sets a flag.
	 * @param flag the flag
	 */
	public final void setFlag(final long flag) {
		flags |= flag;
	}
	/**
	 * Sets the full score for a specific character ability.
	 * @param ability the ability
	 * @param score the new score
	 * @throws Exception if the ability was never defined
	 */
	public final void setFullAbilityScore(final int ability, final float score)
			throws Exception {
		if (ability < 0
				|| ability >= baseAbilityScores.length) {
			PooledStringBuilder sb =
					StringBuilderPool.getInstance().getStringBuilder();
			sb.append("Error - ability ");
			sb.append(ability);
			sb.append(" is outside array bounds.");
			Exception ex = new Exception(sb.toString());
			sb.returnToPool();
			throw ex;
		}
		fullAbilityScores[ability] = score;
	}
	/**
	 * Sets the {@link RPGCharacter}'s gender.
	 * @param val the gender to set
	 */
	public final void setGender(final int val) {
		gender = val;
		notifyWatchers();
	}
	/**
	 * Sets the {@link RPGCharacter}'s level.
	 * @param val the level to set
	 */
	public final void setLevel(final int val) {
		level = val;
		notifyWatchers();
	}
	/**
	 * Sets the {@link RPGCharacter}'s name.
	 * @param val the name to set
	 */
	public final void setName(final char[] val) {
		name = val;
		notifyWatchers();
	}
	/**
	 * Sets the {@link RPGCharacter}'s name.
	 * @param val the name to set
	 */
	public final void setName(final String val) {
		name = val.toCharArray();
		notifyWatchers();
	}
	/**
	 * Sets the {@link RPGCharacter}'s Profession.
	 * @param val the profession to set
	 */
	public void setProfession(final int val) {
		profession = val;
		notifyWatchers();
	}
	/**
	 * Sets the {@link RPGCharacter}'s Race.
	 * @param val the race to set
	 */
	public final void setRace(final int val) {
		race = val;
		notifyWatchers();
	}
	/**
	 * Sets the {@link RPGCharacter}'s experience points.
	 * @param val the amount to set
	 */
	public final void setXp(final long val) {
		xp = val;
		notifyWatchers();
	}
}
